#include "config.h"

#include <sys/types.h>
#include <stdint.h>
#include <dirent.h>

#define DBG_SUBSYS S_LIBINTERFACE

#include "adt.h"
#include "error.h"
#include "lichstor.h"
#include "readdir.h"
#include "nfs_conf.h"
#include "sysy_lib.h"
#include "dbg.h"

/*
 * maximum number of entries in readdir results
 *
 * this is 4096 / 28 (the minimum size of an entry)
 */
/*
 * static readdir_retok size with XDR overhead
 *
 * 88 bytes attributes, 8 bytes verifier, 4 bytes value_follows first entry,
 * 4 bytes eof flag
 */
#define RETOK_SIZE 104

/*
 * static entry size with XDR overhead
 *
 * 8 bytes fid, 4 bytes name length, 8 bytes cookie, 4 byte value_follows
 */
#define ENTRY_SIZE 24

/*
 * size of a name with XDR overhead
 *
 * XDR pads to multiple of 4 bytes
 */
#define NAME_SIZE(x) (((strlen((x)) + 3) / 4) * 4)

/*
 * perform a READDIR operation
 *
 * fd_decomp must be called directly before to fill the stat cache
 */
//int read_dir(const char *path, uint64_t offset, char *verf,
//                      uint32_t count)
int read_dir(const fileid_t *parent, uint64_t offset, char *verf,
                uint32_t count, readdir_ret *res, entry *_entry,
                char *obj)
{
        int ret, delen, eof = 0;
        uint32_t i, real_count;
        fileid_t fid;
        readdir_retok *resok;
        void *de0;
        struct dirent *de;
        char uuid[MAX_NAME_LEN] = {};
        uint64_t offset2 = 0;

        /* we refuse to return more than 4K from READDIR */
        if (count > 4096)
                count = 4096;

        /* account for size of information heading retok structure */
        real_count = RETOK_SIZE;
        /*
         * we are always returning zero as a cookie verifier. one reason for
         * this is that stat() on Windows seems to return cached st_mtime
         * values, which gives spurious NFS3_EBADCOOKIE. btw, here's what
         * Peter Staubach has to say about cookie verifires:
         *
         * "From my viewpoint, the cookieverifier was a failed experiment in
         * NFS v3. the semantics were never well understood nor supported by
         * many local file systems. the Solaris NFs server always returns zeros
         * in the cookieverifier field."
         */
        _memset(verf, 0x0, NFS3_COOKIEVERFSIZE);

        i = 0;
        _entry[0].name = NULL;
        de0 = NULL;
        delen = 0;

        get_uuid(uuid);
        ret = stor_listpool_open(parent, uuid);
        if (ret)
                GOTO(err_ret, ret);

        de0 = NULL;
        while (real_count < count && i < MAX_ENTRIES) {
                ret = stor_listpool(parent, uuid, offset, &de0, &delen);
                if (unlikely(ret)) {
                        res->status = readdir_err(ret);
                        DERROR("ret %d res->status %d\n", ret, res->status);
                        GOTO(err_close, ret);
                }

                if (delen == 0) {
                        eof = 1;
                        break;
                }

                offset2 = 0;
                dir_for_each(de0, delen, de, offset2) {
                        if (strlen(de->d_name) == 0) {
                                eof = 1;
                                goto out;
                        } else if (delen - offset2 < sizeof(*de) + MAX_NAME_LEN)
                                break;

                        offset += de->d_reclen;
                        DBUG("name %s %u de0 %p delen %u de %p d_off %llu\n",
                                        de->d_name, i, de0, delen, de, (LLU)offset);

                        _strcpy(&obj[i * NFS_PATHLEN_MAX], de->d_name);

                        if (i > 0)
                                _entry[i - 1].next = &_entry[i];

                        /* @note */
                        ret = stor_lookup("default", parent, de->d_name, &fid);
                        if (unlikely(ret))
                                GOTO(err_ret, ret);

                        _entry[i].fileid = fid.id;
                        _entry[i].name   = &obj[i * NFS_PATHLEN_MAX];
                        _entry[i].cookie = offset;
                        _entry[i].next   = NULL;

                        /* account for entry size */
                        real_count += ENTRY_SIZE + NAME_SIZE(de->d_name);

                        /* overflowed the maximum size */
                        if ((real_count > count && i > 0) || (i + 1 > MAX_DIRPLUS_ENTRIES)) {
                                _entry[i - 1].next = NULL;
                                break;
                        }

                        i++;
                }

                yfree((void **)&de0);
        }

out:
        if (de0)
                yfree((void **)&de0);

        resok = &res->u.ok;

        if (_entry[0].name)
                resok->reply.entries = &_entry[0];
        else
                resok->reply.entries = NULL;

        if (eof)
                resok->reply.eof = TRUE;
        else
                resok->reply.eof = FALSE;

        _memcpy(resok->cookieverf, verf, NFS3_COOKIEVERFSIZE);

        res->status = NFS3_OK;

        stor_listpool_close(parent, uuid);
        return 0;
err_close:
        stor_listpool_close(parent, uuid);
err_ret:
        return ret;
}

static int __readdirplus(const fileid_t *parent, const char *name, uint64_t offset,
                         char *obj, entryplus *_entryplus,  fileid_t *fharray, int idx)
{
        int ret;
        fileid_t fid;
        struct stat stbuf;

        ret = stor_lookup("default", parent, name, &fid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = stor_getattr("default", &fid, &stbuf);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (idx > 0)
                _entryplus[idx - 1].next = &_entryplus[idx];

        _strcpy(&obj[idx * MAX_NAME_LEN], name);
        fharray[idx] = fid;
        _entryplus[idx].fileid = stbuf.st_ino;
        _entryplus[idx].fh.handle_follows = 1;
        _entryplus[idx].fh.handle.val = (void *)&fharray[idx];
        _entryplus[idx].fh.handle.len = sizeof(fileid_t);
        _entryplus[idx].name   = &obj[idx * MAX_NAME_LEN];
        _entryplus[idx].cookie = offset;
        _entryplus[idx].next   = NULL;

        get_postopattr_stat(&_entryplus[idx].attr, &stbuf);

        return 0;
err_ret:
        return ret;
}

#if 1
int readdirplus(const fileid_t *parent, uint64_t offset, char *verf,
                uint32_t count, readdirplus_ret *res, entryplus *_entryplus,
                char *obj, fileid_t *fharray)
{
        int ret, delen, eof;
        uint32_t i, real_count;
        readdirplus_retok *resok;
        void *de0;
        struct dirent *de;
        uint64_t offset2 = 0;
        char uuid[MAX_NAME_LEN] = {};

        if (count > 4096)
                count = 4096;

        real_count = RETOK_SIZE;
        _memset(verf, 0x0, NFS3_COOKIEVERFSIZE);

        i = 0;
        eof = 0;
        _entryplus[0].name = NULL;
        de0 = NULL;
        delen = 0;

        get_uuid(uuid);

        ret = stor_listpool_open(parent, uuid);
        if (ret)
                GOTO(err_ret, ret);

        de0 = NULL;
        while (real_count < count && i < MAX_DIRPLUS_ENTRIES) {
                ret = stor_listpool(parent, uuid, offset, &de0, &delen);
                if (unlikely(ret)) {
                        res->status = readdir_err(ret);
                        DERROR("ret %d res->status %d\n", ret, res->status);
                        GOTO(err_close, ret);
                }

                if (delen == 0) {
                        DINFO("eof\n");
                        eof = 1;
                        break;
                }

                offset2 = 0;
                dir_for_each(de0, delen, de, offset2) {
                        if (strlen(de->d_name) == 0) {
                                DINFO("eof\n");
                                eof = 1;
                                goto out;
                        } else if (delen - offset2 < sizeof(*de) + MAX_NAME_LEN)
                                break;

                        offset += de->d_reclen;
                        DINFO("name %s %u de0 %p delen %u de %p offset %llu\n",
                              de->d_name, i, de0, delen, de, (LLU)offset);

                        ret = __readdirplus(parent, de->d_name, offset,
                                        obj, _entryplus, fharray, i);
                        if (unlikely(ret)) {
                                continue;
                        }

                        /* account for entry size */
                        real_count += ENTRY_SIZE + NAME_SIZE(de->d_name);

                        /* overflowed the maximum size */
                        if ((real_count > count && i > 0)
                            || (i + 1 > MAX_DIRPLUS_ENTRIES)) {
                                _entryplus[i - 1].next = NULL;
                                break;
                        }

                        i++;
                }

                yfree((void **)&de0);
        }

out:
        if (de0)
                yfree((void **)&de0);

        resok = &res->u.ok;

        if (_entryplus[0].name)
                resok->reply.entries = (void *)&_entryplus[0];
        else
                resok->reply.entries = NULL;

        if (eof == 0)
                resok->reply.eof = FALSE;
        else
                resok->reply.eof = TRUE;

        _memcpy(resok->cookieverf, verf, NFS3_COOKIEVERFSIZE);

        res->status = NFS3_OK;
        stor_listpool_close(parent, uuid);

        return 0;
err_close:
        stor_listpool_close(parent, uuid);
err_ret:
        return ret;
}
#endif
